package org.gdstash.db;

import org.gdstash.util.GDLog;
import org.gdstash.util.GDMsgFormatter;
import org.gdstash.util.GDMsgLogger;

import java.sql.*;
import java.util.LinkedList;
import java.util.List;

public class DBConfig implements Cloneable {
    public static final String TABLE_NAME = "INFO_CONFIG";
    private static final int ROW_ID = 1;
    private static final int ROW_CONFIG_VER = 2;
    private static final int ROW_CONFIG_INIT = 3;
    private static final int ROW_GDDB_VER = 4;
    private static final int ROW_GDDB_INIT = 5;
    private static final int ROW_GD_LOCAL = 6;
    private static final int ROW_MOD_STANDALONE = 7;
    private static final int ROW_NAME_PART_POS1 = 8;
    private static final int ROW_NAME_PART_POS2 = 9;
    private static final int ROW_NAME_PART_POS3 = 10;
    private static final int ROW_NAME_PART_POS4 = 11;
    private static final int ROW_NAME_PART_POS5 = 12;
    public static final int NAME_PART_NONE = 0;
    public static final int NAME_PART_PREFIX = 1;
    public static final int NAME_PART_QUALITY = 2;
    public static final int NAME_PART_STYLE = 3;
    public static final int NAME_PART_NAME = 4;
    public static final int NAME_PART_SUFFIX = 5;
    private static DBConfig singleton;
    public String configVer;
    public boolean configInit;
    public String gddbVer;
    public boolean gddbInit;
    public String gdLocal;
    public boolean modStandalone;
    public int namePart1;
    public int namePart2;
    public int namePart3;
    public int namePart4;
    public int namePart5;

    public DBConfig() {
        this.configVer = "1.0.8";
        this.configInit = false;
        this.gddbVer = "1.8.0";
        this.gddbInit = false;
        this.gdLocal = "";
        this.modStandalone = false;
        this.namePart1 = 1;
        this.namePart2 = 2;
        this.namePart3 = 3;
        this.namePart4 = 4;
        this.namePart5 = 5;
    }

    public Object clone() {
        final DBConfig config = new DBConfig();
        config.configVer = this.configVer;
        config.configInit = this.configInit;
        config.gddbVer = this.gddbVer;
        config.gddbInit = this.gddbInit;
        config.gdLocal = this.gdLocal;
        config.modStandalone = this.modStandalone;
        config.namePart1 = this.namePart1;
        config.namePart2 = this.namePart2;
        config.namePart3 = this.namePart3;
        config.namePart4 = this.namePart4;
        config.namePart5 = this.namePart5;
        return config;
    }

    public static void createTable() throws SQLException {
        final String dropTable = "DROP TABLE INFO_CONFIG";
        final String createTable = "CREATE TABLE INFO_CONFIG (ID             VARCHAR(8)   NOT NULL, CONFIG_VER     VARCHAR(8)   NOT NULL, CONFIG_INIT    BOOLEAN, GDDB_VER       VARCHAR(8)   NOT NULL, GDDB_INIT      BOOLEAN, GD_LOCAL       VARCHAR(128), MOD_STANDALONE BOOLEAN, NAME_PART1     INTEGER, NAME_PART2     INTEGER, NAME_PART3     INTEGER, NAME_PART4     INTEGER, NAME_PART5     INTEGER, PRIMARY KEY (ID))";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final Statement st = conn.createStatement()) {
                DBConfig config = null;
                if (GDDBUtil.tableExists(conn, "INFO_CONFIG")) {
                    config = rescueConfig();
                    st.execute(dropTable);
                }
                st.execute(createTable);
                st.close();
                conn.commit();
                if (config != null) {
                    restoreConfig(config);
                }
            } catch (SQLException ex) {
                conn.rollback();
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    private static void insert(final DBConfig config) throws SQLException {
        final String insert = "INSERT INTO INFO_CONFIG VALUES (?,?,?,?,?,?,?,?,?,?,?,?)";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(insert)) {
                ps.setString(1, "DEFAULT");
                ps.setString(2, config.configVer);
                ps.setBoolean(3, config.configInit);
                ps.setString(4, config.gddbVer);
                ps.setBoolean(5, config.gddbInit);
                ps.setString(6, config.gdLocal);
                ps.setBoolean(7, config.modStandalone);
                ps.setInt(8, config.namePart1);
                ps.setInt(9, config.namePart2);
                ps.setInt(10, config.namePart3);
                ps.setInt(11, config.namePart4);
                ps.setInt(12, config.namePart5);
                ps.executeUpdate();
                ps.close();
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_SAVE_CONFIG"));
                GDMsgLogger.addError(ex);
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static DBConfig get() {
        if (DBConfig.singleton != null) {
            return DBConfig.singleton;
        }
        DBConfig.singleton = new DBConfig();
        final String command = "SELECT * FROM INFO_CONFIG WHERE ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, "DEFAULT");
            try (final ResultSet rs = ps.executeQuery()) {
                final List<DBConfig> list = wrap(rs);
                if (list.isEmpty()) {
                    DBConfig.singleton = null;
                } else {
                    DBConfig.singleton = list.get(0);
                }
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_READ_CONFIG"));
            GDMsgLogger.addError(ex2);
            DBConfig.singleton = rescueConfig();
        }
        return DBConfig.singleton;
    }

    private static List<DBConfig> wrap(final ResultSet rs) throws SQLException {
        final LinkedList<DBConfig> list = new LinkedList<DBConfig>();
        while (rs.next()) {
            final DBConfig config = new DBConfig();
            config.configVer = rs.getString(2);
            config.configInit = rs.getBoolean(3);
            config.gddbVer = rs.getString(4);
            config.gddbInit = rs.getBoolean(5);
            config.gdLocal = rs.getString(6);
            config.modStandalone = rs.getBoolean(7);
            config.namePart1 = rs.getInt(8);
            config.namePart2 = rs.getInt(9);
            config.namePart3 = rs.getInt(10);
            config.namePart4 = rs.getInt(11);
            config.namePart5 = rs.getInt(12);
            list.add(config);
        }
        return list;
    }

    private static DBConfig rescueConfig() {
        final DBConfig config = new DBConfig();
        final String id = getConfigID();
        if (id == null) {
            return null;
        }
        config.configVer = getConfigVersion();
        config.configInit = getConfigInit();
        config.gddbVer = getGDDBVersion();
        config.gddbInit = getGDDBInit();
        config.gdLocal = getGDLocal();
        config.modStandalone = getLoadVanilla();
        return config;
    }

    private static void restoreConfig(final DBConfig config) {
        try {
            insert(config);
        } catch (SQLException ex) {
        }
    }

    private static String getStringValue(final String fieldname) {
        String str = null;
        boolean found = false;
        final String command = "SELECT " + fieldname + " FROM " + "INFO_CONFIG" + " WHERE ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, "DEFAULT");
            try (final ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    str = rs.getString(1);
                    found = true;
                }
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_READ_CONFIG"));
            GDMsgLogger.addError(ex2);
        }
        if (!found) {
            str = null;
        }
        return str;
    }

    public static String getConfigID() {
        return getStringValue("ID");
    }

    public static String getConfigVersion() {
        return getStringValue("CONFIG_VER");
    }

    public static String getGDDBVersion() {
        return getStringValue("GDDB_VER");
    }

    public static String getGDLocal() {
        return getStringValue("GD_LOCAL");
    }

    private static int getIntValue(final String fieldname) {
        int i = -1;
        boolean found = false;
        final String command = "SELECT " + fieldname + " FROM " + "INFO_CONFIG" + " WHERE ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, "DEFAULT");
            try (final ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    i = rs.getInt(1);
                    found = true;
                }
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_READ_CONFIG"));
            GDMsgLogger.addError(ex2);
        }
        if (!found) {
            i = -1;
        }
        return i;
    }

    public static boolean getBoolValue(final String fieldname, final boolean defaultVal) {
        boolean flag = defaultVal;
        boolean found = false;
        final String command = "SELECT " + fieldname + " FROM " + "INFO_CONFIG" + " WHERE ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, "DEFAULT");
            try (final ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    flag = rs.getBoolean(1);
                    found = true;
                }
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_READ_CONFIG"));
            GDMsgLogger.addError(ex2);
        }
        if (!found) {
            flag = defaultVal;
        }
        return flag;
    }

    public static boolean getConfigInit() {
        return getBoolValue("CONFIG_INIT", false);
    }

    public static boolean getGDDBInit() {
        return getBoolValue("GDDB_INIT", false);
    }

    public static boolean getLoadVanilla() {
        return getBoolValue("MOD_STANDALONE", false);
    }

    public static boolean setVersions(final String configVer, final String gddbVer) {
        String command = null;
        boolean success = false;
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            if (configExists(conn)) {
                command = "UPDATE INFO_CONFIG SET CONFIG_VER = ?, GDDB_VER = ? WHERE ID = ?";
                try (final PreparedStatement ps = conn.prepareStatement(command)) {
                    ps.setString(1, configVer);
                    ps.setString(2, gddbVer);
                    ps.setString(3, "DEFAULT");
                    ps.executeUpdate();
                    ps.close();
                    conn.commit();
                    success = true;
                } catch (SQLException ex) {
                    conn.rollback();
                    throw ex;
                } finally {
                    conn.setAutoCommit(auto);
                }
            } else {
                try {
                    final DBConfig config = new DBConfig();
                    config.configVer = configVer;
                    config.gddbVer = gddbVer;
                    insert(config);
                    success = true;
                } catch (SQLException ex) {
                    success = false;
                    throw ex;
                }
            }
        } catch (SQLException ex2) {
            GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_SAVE_CONFIG"));
            GDMsgLogger.addError(ex2);
        }
        return success;
    }

    public static boolean setConfigInit(final String configVer, final String gddbVer, final boolean init) {
        String command = null;
        boolean success = false;
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            if (configExists(conn)) {
                command = "UPDATE INFO_CONFIG SET CONFIG_INIT = ? WHERE ID = ?";
                try (final PreparedStatement ps = conn.prepareStatement(command)) {
                    ps.setBoolean(1, init);
                    ps.setString(2, "DEFAULT");
                    ps.executeUpdate();
                    ps.close();
                    conn.commit();
                    success = true;
                } catch (SQLException ex) {
                    conn.rollback();
                    throw ex;
                } finally {
                    conn.setAutoCommit(auto);
                }
            } else {
                try {
                    final DBConfig config = new DBConfig();
                    config.configVer = configVer;
                    config.gddbVer = gddbVer;
                    config.configInit = init;
                    insert(config);
                    success = true;
                } catch (SQLException ex) {
                    success = false;
                    throw ex;
                }
            }
        } catch (SQLException ex2) {
            GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_SAVE_CONFIG"));
            GDMsgLogger.addError(ex2);
        }
        return success;
    }

    public static boolean setGDDBInit(final String configVer, final String gddbVer, final boolean init) {
        String command = null;
        boolean success = false;
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            if (configExists(conn)) {
                command = "UPDATE INFO_CONFIG SET GDDB_INIT = ? WHERE ID = ?";
                try (final PreparedStatement ps = conn.prepareStatement(command)) {
                    ps.setBoolean(1, init);
                    ps.setString(2, "DEFAULT");
                    ps.executeUpdate();
                    ps.close();
                    conn.commit();
                    success = true;
                } catch (SQLException ex) {
                    conn.rollback();
                    throw ex;
                } finally {
                    conn.setAutoCommit(auto);
                }
            } else {
                try {
                    final DBConfig config = new DBConfig();
                    config.configVer = configVer;
                    config.gddbVer = gddbVer;
                    config.gddbInit = init;
                    insert(config);
                    success = true;
                } catch (SQLException ex) {
                    success = false;
                    throw ex;
                }
            }
        } catch (SQLException ex2) {
            GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_SAVE_CONFIG"));
            GDMsgLogger.addError(ex2);
        }
        return success;
    }

    public static boolean setItemNameOrder(final String configVer, final String gddbVer, final String order) {
        if (order == null) {
            return false;
        }
        final GDLog tempLog = new GDLog();
        final int[] nameOrder = determineItemNameOrder(order, tempLog);
        if (tempLog.containsErrors()) {
            GDMsgLogger.addLog(tempLog);
            return false;
        }
        return writeItemNameOrder(configVer, gddbVer, nameOrder);
    }

    private static int[] determineItemNameOrder(final String order, final GDLog log) {
        final int[] nameOrder = new int[5];
        for (int i = 0; i < nameOrder.length; ++i) {
            nameOrder[i] = 0;
        }
        int posOrder = 0;
        String rest;
        int pos;
        for (rest = order; !rest.isEmpty(); rest = rest.substring(pos + 1)) {
            pos = rest.indexOf("}");
            if (pos == -1) {
                break;
            }
            final String sTag = rest.substring(0, pos + 1);
            final String sPart = sTag.substring(sTag.length() - 2, sTag.length() - 1);
            try {
                int iPart = Integer.valueOf(sPart);
                if (iPart > 4) {
                    throw new NumberFormatException();
                }
                ++iPart;
                nameOrder[posOrder] = iPart;
                ++posOrder;
            } catch (NumberFormatException ex) {
                break;
            }
        }
        if (!rest.isEmpty()) {
            final Object[] args = {order};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_GENDER_TAG", args);
            GDMsgLogger.addError(msg);
            return null;
        }
        return nameOrder;
    }

    private static boolean writeItemNameOrder(final String configVer, final String gddbVer, final int[] order) {
        String command = null;
        boolean success = false;
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            if (configExists(conn)) {
                command = "UPDATE INFO_CONFIG SET NAME_PART1 = ?, NAME_PART2 = ?, NAME_PART3 = ?, NAME_PART4 = ?, NAME_PART5 = ? WHERE ID = ?";
                try (final PreparedStatement ps = conn.prepareStatement(command)) {
                    ps.setInt(1, order[0]);
                    ps.setInt(2, order[1]);
                    ps.setInt(3, order[2]);
                    ps.setInt(4, order[3]);
                    ps.setInt(5, order[4]);
                    ps.setString(6, "DEFAULT");
                    ps.executeUpdate();
                    ps.close();
                    conn.commit();
                    success = true;
                } catch (SQLException ex) {
                    conn.rollback();
                    throw ex;
                } finally {
                    conn.setAutoCommit(auto);
                }
            } else {
                try {
                    final DBConfig config = new DBConfig();
                    config.configVer = configVer;
                    config.gddbVer = gddbVer;
                    config.namePart1 = order[0];
                    config.namePart2 = order[1];
                    config.namePart3 = order[2];
                    config.namePart4 = order[3];
                    config.namePart5 = order[4];
                    insert(config);
                    success = true;
                } catch (SQLException ex) {
                    success = false;
                    throw ex;
                }
            }
        } catch (SQLException ex2) {
            GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_SAVE_CONFIG"));
            GDMsgLogger.addError(ex2);
        }
        return success;
    }

    public static boolean setConfigData(final String configVer, final String gddbVer, final DBConfig config) {
        String command = null;
        boolean success = false;
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            if (configExists(conn)) {
                command = "UPDATE INFO_CONFIG SET GD_LOCAL = ?, MOD_STANDALONE = ? WHERE ID = ?";
                try (final PreparedStatement ps = conn.prepareStatement(command)) {
                    ps.setString(1, config.gdLocal);
                    ps.setBoolean(2, config.modStandalone);
                    ps.setString(3, "DEFAULT");
                    ps.executeUpdate();
                    ps.close();
                    conn.commit();
                    success = true;
                } catch (SQLException ex) {
                    conn.rollback();
                    throw ex;
                } finally {
                    conn.setAutoCommit(auto);
                }
            } else {
                try {
                    final DBConfig cfg = new DBConfig();
                    cfg.configVer = configVer;
                    cfg.gddbVer = gddbVer;
                    cfg.gdLocal = config.gdLocal;
                    cfg.modStandalone = config.modStandalone;
                    insert(cfg);
                    success = true;
                } catch (SQLException ex) {
                    success = false;
                    throw ex;
                }
            }
        } catch (SQLException ex2) {
            GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_SAVE_CONFIG"));
            GDMsgLogger.addError(ex2);
        }
        return success;
    }

    public static void reset() {
        DBConfig.singleton = null;
    }

    private static boolean configExists(final Connection conn) throws SQLException {
        final String select = "SELECT CONFIG_VER FROM INFO_CONFIG WHERE ID = ?";
        boolean found = false;
        if (!GDDBUtil.tableExists(conn, "INFO_CONFIG")) {
            createTable();
        }
        final String version = getConfigVersion();
        if (version == null || !version.equals("1.0.8")) {
            createTable();
        }
        try (final PreparedStatement ps = conn.prepareStatement(select)) {
            if (!GDDBUtil.tableExists(conn, "INFO_CONFIG")) {
                return false;
            }
            ps.setString(1, "DEFAULT");
            final ResultSet rs = ps.executeQuery();
            if (rs.next()) {
                found = true;
            }
            conn.commit();
        } catch (SQLException ex) {
        }
        return found;
    }
}
